package com.df.my_net_game.common.config;

import com.alibaba.fastjson2.JSON;
import com.df.my_net_game.common.utils.LogicControllerData;
import com.df.my_net_game.common.utils.LogicMethodData;
import com.df.my_net_game.common.utils.LogicMethodMgr;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioChannelOption;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.stream.ChunkedNioFile;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.CharsetUtil;
import io.netty.util.internal.StringUtil;
import jakarta.annotation.PostConstruct;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.runtime.resource.loader.JarResourceLoader;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;

import static io.netty.handler.codec.http.HttpUtil.is100ContinueExpected;

@Slf4j
public class GameDocServer extends Thread {


    private static ConcurrentHashMap<Integer,CtlMetaInfo> cache= new ConcurrentHashMap<>();


    @Override
    public void run() {
        startServer();
    }

    private void startServer ()  {
        ServerBootstrap bootstrap = new ServerBootstrap();
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup work = new NioEventLoopGroup();
        try {
            bootstrap.group(boss,work)
                    .handler(new LoggingHandler(LogLevel.DEBUG))
                    .channel(NioServerSocketChannel.class)
                    .childOption(ChannelOption.SO_KEEPALIVE,true)
                    .childOption(NioChannelOption.SO_KEEPALIVE,true)
                    .childHandler(new HttpServerInitializer());

            ChannelFuture f = bootstrap.bind(new InetSocketAddress(6789)).sync();
            System.out.println("文档服务器启动成功:http://localhost:6789");
            f.channel().closeFuture().sync();
        }catch (Exception e){
            log.error("文档服务器启动失败");
            e.printStackTrace();
        }

    }
    private static class HttpServerInitializer extends ChannelInitializer<SocketChannel> {

        @Override
        protected void initChannel(SocketChannel channel) throws Exception {
            ChannelPipeline pipeline = channel.pipeline();
            pipeline.addLast(new IdleStateHandler(60,0,0, TimeUnit.SECONDS));
            pipeline.addLast(new HttpServerCodec());// http 编解码
            pipeline.addLast("httpAggregator",new HttpObjectAggregator(512*1024)); // http 消息聚合器                                                                     512*1024为接收的最大contentlength
            pipeline.addLast(new StaticFileHandler());// 静态文件请求处理器
            pipeline.addLast(new HttpRequestHandler());// 请求处理器

        }
    }
    @Data
    private static class Param{
        private String name;
        private String javaType;
        private String jsonExample;
    }

    @Data
    private static class MethodMetaInfo{
        private Integer id;
        private String englishName;
        private String name;
        private Integer methodFullId;

        private List<Param> params = new ArrayList<>();
        private List<Param> returns = new ArrayList<>();
    }

    @Data
    private static class CtlMetaInfo{
        private Integer id;
        private String name;
        private String englishName;
        private String description;
        private List<MethodMetaInfo> methods = new ArrayList<>();
    }



    private static class HttpRequestHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
            String uri = req.getUri().trim();
            DefaultFullHttpResponse resp = new DefaultFullHttpResponse(req.protocolVersion(),HttpResponseStatus.NOT_FOUND);
            resp.headers().setInt(HttpHeaderNames.CONTENT_LENGTH,0);
            try {
                //获取控制权列表
                if(uri.equals("/doc/ctrollers")){
                    ConcurrentHashMap<Integer, LogicControllerData> datas = LogicMethodMgr.getDatas();
                    List<Object> ctls = new ArrayList<>();
                    for (Map.Entry<Integer, LogicControllerData> entry : datas.entrySet()) {
                        Map<String,Object> ctlMeta = new HashMap<>();
                        ctlMeta.put("id",entry.getKey());
                        ctlMeta.put("name",entry.getValue().getControllerName());
                        ctlMeta.put("englishName",entry.getValue().getControllerClassType().getSimpleName());
                        ctls.add(ctlMeta);
                    }
                    String jsonString = JSON.toJSONString(ctls);
                    byte[] bytes = jsonString.getBytes(StandardCharsets.UTF_8);
                    resp.setStatus(HttpResponseStatus.OK);
                    resp.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
                    resp.content().writeBytes(bytes);
                    resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
                    ctx.writeAndFlush(resp);
                    return;
                }
                //获取控制器详细信息
                if(uri.startsWith("/doc/ctroller/")){
                    String replace = uri.replace("/doc/ctroller/", "");
                    int i = replace.indexOf("?");
                    if(i!=-1){
                        replace = replace.substring(0,i);
                    }
                    if(replace.trim().equals("")){
                        resp.setStatus(HttpResponseStatus.BAD_REQUEST);
                        ctx.writeAndFlush(resp);
                        return;
                    }
                    Integer cmdId = Integer.valueOf(replace);
                    CtlMetaInfo ctlMetaInfo = cache.get(cmdId);
                    if(ctlMetaInfo!=null){
                        String jsonString = JSON.toJSONString(ctlMetaInfo);
                        byte[] bytes = jsonString.getBytes(StandardCharsets.UTF_8);
                        resp.setStatus(HttpResponseStatus.OK);
                        resp.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
                        resp.content().writeBytes(bytes);
                        resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
                        ctx.writeAndFlush(resp);
                        return;
                    }
                    LogicControllerData logicControllerData = LogicMethodMgr.getDatas().get(cmdId);
                    if(logicControllerData==null){
                        resp.setStatus(HttpResponseStatus.OK);
                        ctx.writeAndFlush(resp);
                        return;
                    }
                    CtlMetaInfo info = new CtlMetaInfo();
                    info.setId(cmdId);
                    info.setName(logicControllerData.getControllerName());
                    info.setEnglishName(logicControllerData.getControllerClassName());
                    info.setDescription(logicControllerData.getControllerDesc());
                    Collection<LogicMethodData> values = logicControllerData.getMethos().values();
                    for (LogicMethodData value : values) {
                        MethodMetaInfo methodMetaInfo = new MethodMetaInfo();
                        methodMetaInfo.setId(value.getMethodId());
                        methodMetaInfo.setEnglishName(value.getMethodName());
                        methodMetaInfo.setName(value.getMethodDesc());
                        methodMetaInfo.setMethodFullId(cmdId*10000+value.getMethodId());

                        Param param = new Param();
                        param.setName(value.getParamName());
                        param.setJavaType(value.getParamType().getName());
                        param.setJsonExample("req demo");
                        methodMetaInfo.params.add(param);

                        Param retunP = new Param();
                        retunP.setJavaType(value.getReturnType().getName());
                        retunP.setJsonExample("return demo");
                        methodMetaInfo.returns.add(retunP);

                        info.getMethods().add(methodMetaInfo);
                    }
                    cache.put(cmdId,info);
                    String jsonString = JSON.toJSONString(info);
                    byte[] bytes = jsonString.getBytes(StandardCharsets.UTF_8);
                    resp.setStatus(HttpResponseStatus.OK);
                    resp.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length);
                    resp.content().writeBytes(bytes);
                    resp.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/json; charset=UTF-8");
                    ctx.writeAndFlush(resp);
                    return;
                }
            }catch (Exception e){
                resp.setStatus(HttpResponseStatus.INTERNAL_SERVER_ERROR);
                e.printStackTrace();
                ctx.writeAndFlush(resp);
            }

        }
    }

    private static class StaticFileHandler extends SimpleChannelInboundHandler<FullHttpRequest> {

        @Override
        public void channelReadComplete(ChannelHandlerContext ctx) {
            ctx.flush();
        }

        @Override
        protected void channelRead0(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
            //100 Continue
            if (is100ContinueExpected(req)) {
                ctx.write(new DefaultFullHttpResponse(
                        HttpVersion.HTTP_1_1,
                        HttpResponseStatus.CONTINUE));
            }

            String uri = req.uri();
            if(uri.equals("/") || !uri.startsWith("/doc/")){
                respseStaticFile(req, ctx);
                return;
            }
            //SimpleChannelInboundHandler在调用完channelRead0后会自动进行资源释放，但是我们此处希望req继续到下一个handler处理，因此引用要增加
            req.retain();
            ctx.fireChannelRead(req);
        }

        private void respseStaticFile(FullHttpRequest req, ChannelHandlerContext ctx) {

            DefaultFullHttpResponse resp = new DefaultFullHttpResponse(req.protocolVersion(),HttpResponseStatus.NOT_FOUND);
            resp.headers().setInt(HttpHeaderNames.CONTENT_LENGTH,0);
            String uri = req.getUri();
            // hello/a.js
            if(uri.trim().equals("") || uri.trim().equals("/")){
                uri = "index.html";
            }
            if(uri.startsWith("/")){
                uri = uri.substring(1);
            }
            String path = this.getClass().getResource("/").getPath();
            path = path+"static"+File.separator+getFilePath(uri);
            try (FileChannel fileChannel = new FileInputStream(path).getChannel()){

                resp.setStatus(HttpResponseStatus.OK);
                int size = (int) fileChannel.size();
                resp.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, size);
                resp.content().writeBytes(fileChannel,0,size);
                setContentType(resp,uri);
                ctx.writeAndFlush(resp);
            }catch (Exception e){
                ctx.writeAndFlush(resp);
            }
        }

        private String getFilePath(String uri) {
            String[] split = uri.split("/");
            StringBuilder sb = new StringBuilder();

            for (int i = 0; i < split.length; i++) {
                sb.append(split[i]);
                if(i!=split.length-1){
                    sb.append(File.separator);
                }
            }
            return sb.toString();
        }

        private static void setContentType(HttpResponse response, String uri) {
            //MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
            if (uri.endsWith(".js")) {
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "application/x-javascript");
            } else if (uri.endsWith(".css")) {
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/css; charset=UTF-8");
            } else if (uri.endsWith(".html")) {
                response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");

            }
        }

    }
}
